#region Using Statements
using System;
using System.IO;
using System.Collections.ObjectModel;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;
#endregion

namespace SkyBurner
{
    #region MOODTriangle class
    public class MOODTriangle
    {
        #region Private Attributes
        private Vector3[] m_Vertices;
        private Vector3[] m_Normals;
        private Plane m_Plane;
        #endregion

        #region Public Properties
        public Vector3 V0
        {
            get { return m_Vertices[0]; }
        }

        public Vector3 V1
        {
            get { return m_Vertices[1]; }
        }

        public Vector3 V2
        {
            get { return m_Vertices[2]; }
        }

        public Vector3 Normal0
        {
            get { return m_Normals[0]; }
        }

        public Vector3 Normal1
        {
            get { return m_Normals[1]; }
        }
        
        public Vector3 Normal2
        {
            get { return m_Normals[2]; }
        }

        public Plane Plane
        {
            get { return m_Plane; }
        }
        #endregion

        public MOODTriangle(Vector3 vVertex0, Vector3 vVertex1, Vector3 vVertex2, Vector3 vNormal0, Vector3 vNormal1, Vector3 vNormal2)
        {
            m_Vertices = new Vector3[3];
            m_Vertices[0] = vVertex0;
            m_Vertices[1] = vVertex1;
            m_Vertices[2] = vVertex2;

            m_Normals = new Vector3[3];
            m_Normals[0] = vNormal0;
            m_Normals[1] = vNormal1;
            m_Normals[2] = vNormal2;

            m_Plane = new Plane(m_Vertices[0], m_Vertices[1], m_Vertices[2]);
        }
    };
    #endregion

    #region MOODMaterial class
    public class MOODMaterial
    {
        #region Private Attributes
        private Texture2D m_Texture;
        private Vector4 m_colorAmbient;
        private Vector4 m_colorDiffuse;
        private Vector4 m_colorSpecular;
        private Vector4 m_colorEmissive;
        #endregion

        #region Public Properties
        public Texture2D Texture
        {
            get { return m_Texture; }
            set { m_Texture = value; }
        }

        public Vector4 Ambient
        {
            get { return m_colorAmbient; }
            set { m_colorAmbient = value; }
        }

        public Vector4 Diffuse
        {
            get { return m_colorDiffuse; }
            set { m_colorDiffuse = value; }
        }

        public Vector4 Specular
        {
            get { return m_colorSpecular; }
            set { m_colorSpecular = value; }
        }
        
        public Vector4 Emissive
        {
            get { return m_colorEmissive; }
            set { m_colorEmissive = value; }
        }
        #endregion

        public MOODMaterial(BaseGame pGame, BinaryReader pBinReader, string pTexturePath)
        {
            String textureName;
            int materialTextureSize;

            // load material texture...
            materialTextureSize = pBinReader.ReadInt32();
            if (materialTextureSize > 0)
            {
                textureName = new String(pBinReader.ReadChars(materialTextureSize));
                m_Texture = pGame.ContentManager.Load<Texture2D>(pTexturePath + "\\" + Path.GetFileNameWithoutExtension(textureName));
            }
            else
                m_Texture = null;

            // load material infos...
            m_colorAmbient = LoadVector4(pBinReader);
            m_colorDiffuse = LoadVector4(pBinReader);
            m_colorSpecular = LoadVector4(pBinReader);
            m_colorEmissive = LoadVector4(pBinReader);
        }

        private Vector4 LoadVector4(BinaryReader pBinReader)
        {
            return new Vector4(pBinReader.ReadSingle(),
                               pBinReader.ReadSingle(),
                               pBinReader.ReadSingle(),
                               pBinReader.ReadSingle());
        }
    }
    #endregion

    #region MOODGroup class
    public class MOODGroup
    {
        #region Private Attributes
        private String m_Name;
        private int m_nMaterialIndex;
        private int m_nTriangleCount;
        private VertexPositionNormalTexture[] m_Vertices;
        private VertexDeclaration m_VertexDecl;
        private VertexBuffer m_VertexBuff;
        #endregion

        #region Public Properties
        public String Name
        {
            get { return m_Name; }
        }

        public int TriangleCount
        {
            get { return m_nTriangleCount; }
        }

        public VertexDeclaration VertexDecl
        {
            get { return m_VertexDecl; }
        }

        public VertexBuffer VertexBuffer
        {
            get { return m_VertexBuff; }
        }

        public int MaterialIndex
        {
            get { return m_nMaterialIndex; }
        }

        public VertexPositionNormalTexture[] Vertices
        {
            get { return m_Vertices; }
        }
        #endregion

        public MOODGroup(BaseGame pGame, BinaryReader pBinReader)
        {
            int NameSize;

            NameSize = pBinReader.ReadInt32();
            m_Name = new String(pBinReader.ReadChars(NameSize));

            m_nMaterialIndex = pBinReader.ReadInt32();
            m_nTriangleCount = pBinReader.ReadInt32();

            int VertexCount = m_nTriangleCount * 3;
            if (VertexCount > 0)
            {
                m_Vertices = new VertexPositionNormalTexture[m_nTriangleCount * 3];
                for (int i = 0; i < VertexCount; i++)
                    m_Vertices[i] = LoadVertex(pBinReader);

                // setup vertex declaration and vertex buffer...
                m_VertexDecl = new VertexDeclaration(pGame.GraphicsDevice, VertexPositionNormalTexture.VertexElements);

                m_VertexBuff = new VertexBuffer(pGame.GraphicsDevice, VertexPositionNormalTexture.SizeInBytes * VertexCount, ResourceUsage.None);
                m_VertexBuff.SetData<VertexPositionNormalTexture>(m_Vertices);
            }
        }

        private VertexPositionNormalTexture LoadVertex(BinaryReader pBinReader)
        {
            VertexPositionNormalTexture returnVertex = new VertexPositionNormalTexture();

            returnVertex.Position = new Vector3(pBinReader.ReadSingle(), pBinReader.ReadSingle(), pBinReader.ReadSingle());
            returnVertex.Normal = new Vector3(pBinReader.ReadSingle(), pBinReader.ReadSingle(), pBinReader.ReadSingle());
            returnVertex.TextureCoordinate = new Vector2(pBinReader.ReadSingle(), pBinReader.ReadSingle());

            return returnVertex;
        }
    }
    #endregion

    public partial class MOODModel : BaseDrawableObject
    {       

        #region Private attributes
        private String m_graphicsPath;
        private const String moodSignature = "m00d";
        private const int moodVersion = 1;

        private String m_Signature;
        private int m_Version;
        private int m_MaterialCount;
        private int m_GroupCount;
        private Collection<MOODMaterial> m_Materials = new Collection<MOODMaterial>();
        private Collection<MOODGroup> m_MeshGroups = new Collection<MOODGroup>();
        private Collection<MOODTriangle> m_Triangles = new Collection<MOODTriangle>();
        private Collection<Vector3> m_Vertices = new Collection<Vector3>();
        private BoundingBoxEx m_boundingBox = new BoundingBoxEx();
        private BasicEffect m_moodBasicEffect;
        #endregion

        #region Public Properties
        public BoundingBoxEx BoundingBox
        {
            get { return m_boundingBox; }
        }

        public Collection<MOODTriangle> Triangles
        {
            get { return m_Triangles; }
        }

        public Collection<MOODGroup> Groups
        {
            get { return m_MeshGroups; }
        }

        public Collection<MOODMaterial> Materials
        {
            get { return m_Materials; }
        }

        public Collection<Vector3> Vertices
        {
            get { return m_Vertices; }
        }
        #endregion

        public MOODModel(BaseGame game, String pMOODModelFile)
            : base(game)
        {
            m_graphicsPath = game.TexturePath;
            m_moodBasicEffect = new BasicEffect(m_Game.GraphicsDevice, null);

            Load(pMOODModelFile);
        }

        public bool Collide(MOODModel pMOODModel)
        {
            return m_boundingBox.Intersect(pMOODModel.m_boundingBox);
        }

        /// <summary>
        /// Allows the game component to update itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        public override void Update(GameTime gameTime, Camera pActiveCamera)
        {
            base.Update(gameTime, pActiveCamera);

            // transform bounding box for collision detection...
            m_boundingBox.Transform(m_mxWorld);
        }

        public override void Draw(GameTime gameTime, Camera pActiveCamera)
        {
            m_Game.GraphicsDevice.RenderState.CullMode = CullMode.None;
            m_Game.GraphicsDevice.RenderState.DepthBufferEnable = true;
            m_Game.GraphicsDevice.RenderState.DepthBufferWriteEnable = true;

            m_moodBasicEffect.World = m_mxWorld;
            m_moodBasicEffect.View = m_mxView;
            m_moodBasicEffect.Projection = m_mxProjection;

            foreach (MOODGroup moodGroup in m_MeshGroups)
            {
                if (moodGroup.TriangleCount <= 0)
                    continue;

                m_Game.GraphicsDevice.VertexDeclaration = moodGroup.VertexDecl;

                MOODMaterial moodMaterial;
                if (moodGroup.MaterialIndex >= 0)
                {
                    moodMaterial = m_Materials[moodGroup.MaterialIndex];
                    if (moodMaterial.Texture!= null)
                    {
                        m_moodBasicEffect.TextureEnabled = true;
                        m_moodBasicEffect.Texture = moodMaterial.Texture;
                    }
                    m_moodBasicEffect.DiffuseColor = new Vector3(moodMaterial.Diffuse.X, moodMaterial.Diffuse.Y, moodMaterial.Diffuse.Z);
                    m_moodBasicEffect.AmbientLightColor = new Vector3(moodMaterial.Ambient.X, moodMaterial.Ambient.Y, moodMaterial.Ambient.Z);
                    m_moodBasicEffect.SpecularColor = new Vector3(moodMaterial.Specular.X, moodMaterial.Specular.Y, moodMaterial.Specular.Z);
                    m_moodBasicEffect.EmissiveColor = new Vector3(moodMaterial.Emissive.X, moodMaterial.Emissive.Y, moodMaterial.Emissive.Z);
                }

                m_moodBasicEffect.Begin();
                foreach (EffectPass pass in m_moodBasicEffect.CurrentTechnique.Passes)
                {
                    pass.Begin();
                    m_Game.GraphicsDevice.Vertices[0].SetSource(moodGroup.VertexBuffer, 0, VertexPositionNormalTexture.SizeInBytes);
                    m_Game.GraphicsDevice.DrawPrimitives(PrimitiveType.TriangleList, 0, moodGroup.TriangleCount);
                    pass.End();
                }
                m_moodBasicEffect.End();
            }
        }

        protected void InitBoundingBox()
        {
            for (int i = 0; i < m_Vertices.Count; i++)
            {
                m_boundingBox.Min = Vector3.Min(m_boundingBox.Min, m_Vertices[i]);
                m_boundingBox.Max = Vector3.Max(m_boundingBox.Max, m_Vertices[i]);
            }          
        }

        protected void Load(String pMOODModelFile)
        {
            FileStream moodFile = new FileStream(pMOODModelFile, FileMode.Open);
            BinaryReader binReader = new BinaryReader(moodFile);

            // load MOOD header settings...
            m_Signature = new String(binReader.ReadChars(4));
            m_Version = binReader.ReadInt32();
            m_MaterialCount = binReader.ReadInt32();
            m_GroupCount = binReader.ReadInt32();

            // check signature and version...
            if ((m_Signature.CompareTo(moodSignature) != 0) || (m_Version != moodVersion))
                throw new Exception("tcMOODModel.Load: Invalid signature or version for MOOD model");

            // load materials...
            for (int i = 0; i < m_MaterialCount; i++)
                m_Materials.Add(new MOODMaterial(m_Game, binReader, m_graphicsPath));

            // load mesh groups...
            for (int i = 0; i < m_GroupCount; i++)
            {
                MOODGroup MeshGroup = new MOODGroup(m_Game, binReader);

                // unroll all triangles...
                if (MeshGroup.TriangleCount > 0)
                {
                    for (int v = 0; v < MeshGroup.Vertices.Length; v += 3)
                    {
                        MOODTriangle moodTriangle = new MOODTriangle(MeshGroup.Vertices[v].Position,
                                                                     MeshGroup.Vertices[v + 1].Position,
                                                                     MeshGroup.Vertices[v + 2].Position,
                                                                     MeshGroup.Vertices[v].Normal,
                                                                     MeshGroup.Vertices[v + 1].Normal,
                                                                     MeshGroup.Vertices[v + 2].Normal);
                        m_Triangles.Add(moodTriangle);

                        m_Vertices.Add(MeshGroup.Vertices[v].Position);
                        m_Vertices.Add(MeshGroup.Vertices[v + 1].Position);
                        m_Vertices.Add(MeshGroup.Vertices[v + 2].Position);
                    }
                }

                m_MeshGroups.Add(MeshGroup);
            }

            // setup bounding box
            InitBoundingBox();

            moodFile.Close();
        }
    }
}
